<?php
abstract class ModelArray extends ModelBase
{
    protected $rows = array();
    protected $sortField;
    protected $sortAsc;
    protected $condition;

    public function ModelArray()
    {
        parent::__construct();
        $this->LoadRows();
    }

    protected function LoadRows()
    {
        $file = $this->GetDataFile();
        if (file_exists($file)) include($file);
    }

    public function Find($condition = null, $fields = null, $orderby = null, $limit = null, &$page = null, &$count = null, $groupby = null)
    {
        $items = $this->rows;
        if ($condition)
        {
            $pk = $this->GetPrimaryKey();
            if ($condition->IsExisted($pk))
            {
                $pk_value = $condition->GetValue($pk);
                return isset($this->rows[$pk_value]) ? array($this->rows[$pk_value]) : array();
            }

            $this->condition = $condition;
            $items = array_filter($items, array($this, 'FilterCallback'));
        }

        if ($orderby)
            $this->Sort($items, $orderby);

        $count = count($items);

        if ($limit)
        {
            if (!$page) $page = 1;
            $offset = ($page-1) * $limit;
            $items = array_slice($items, $offset, $limit);
        }

        return $items;
    }

    protected function Sort(&$items, $orderby)
    {
        $sort = explode(' ', $orderby);
        $this->sortField = $sort[0];
        $this->sortAsc = isset($sort[1]) ? strcasecmp($sort[1], 'ASC') == 0 : true;
        return uasort($items, array($this, 'SortCallback'));
    }

    protected function SortCallback($a, $b)
    {
        if (is_numeric($a[$this->sortField]) && is_numeric($b[$this->sortField]))
            $out = $a[$this->sortField] - $b[$this->sortField];
        else
            $out = strcmp($a[$this->sortField], $b[$this->sortField]);
        return $this->sortAsc ? $out : 0-$out;
    }

    protected function FilterCallback($item)
    {
        foreach ($this->condition->params as $key => $param)
        {
            $key = String::Remove($this->GetTableName().'.', $key);
            $filter = $param['value'];
            if (String::Contains('%', $filter))
            {
                $filter = String::Remove('%', $filter);
                if (!String::Contains($filter, $item[$key]))
                    return false;
            }
            else
            {
                if (!$this->CompareFieldValue($key, $filter, $item[$key]))
                    return false;
            }
        }
        return true;
    }

    protected function CompareFieldValue($field_name, $filter, $value)
    {
        if ($filter == 'NULL')
            return !$value;

        $field = $this->GetField($field_name);
        if ($field->IsString())
            return strcasecmp($filter, $value) == 0;
        else
            return $filter == $value;
    }

    public function FindDistinct($field, $orderby = null, $condition = null)
    {
        $items = array_unique(Set::Pluck($this->rows, $field));
        sort($items);
        $out = array();
        foreach ($items as $item)
        {
            if ($item)
            {
                $tmp[$field] = $item;
                $out[] = $tmp;
            }
        }
        return $out;
    }

    protected function SetRow($id, $row)
    {
        $this->rows[$id] = $row;
    }

    protected function RemoveRow($id)
    {
        unset($this->rows[$id]);
    }

    protected function GetRow($id)
    {
        return $this->rows[$id];
    }

    protected function AddRow()
    {
        $data = func_get_args();
        foreach ($this->fields as $field_name => $field)
        {
            $item[$field_name] = count($data)>0 ? array_shift($data) : null;
        }
        $id = $this->GetPrimaryKeyValue($item);
        if ($id)
            $this->rows[$id] = $item;
        else
            $this->rows[] = $item;
    }

    protected function DoUpdate($item)
    {
        $new_id = $this->GetNewId($item);
        $pk = $this->GetPrimaryKey();
        $id =  $item[$pk];
        $row = $this->GetRow($id);

        foreach ($item as $key => $value)
        {
            if ($key == self::FIELD_NEW_ID)
                $row[$pk] = $new_id;
            else
            {
                try
                {
                    $field = $this->GetField($key);
                    $row[$key] = $this->GetFieldValue($item, $field);
                }
                catch (Exception $ex)
                {}
            }
        }

        if ($new_id)
        {
            $this->RemoveRow($id);
            $this->SetRow($new_id, $row);
        }
        else
            $this->SetRow($id, $row);

        $this->SaveRows();
        return $new_id ? $new_id : $id;
    }

    protected function SaveRows()
    {
        ksort($this->rows);
        $content = $this->GetPhpExport($this->rows);
        return FileHandler::Write($this->GetDataFile(), $content);
    }

    protected function DoDel($id)
    {
        $this->RemoveRow($id);
        return $this->SaveRows();
    }

    protected function DoInsert($item)
    {
        $pk = $this->GetPrimaryKey();
        $id = $this->GetNewId($item);
        if (!$id)
        {
            if (isset($this->fields[$pk]) && $this->fields[$pk]->autoIncrement)
                $id = $this->GetAutoIncrement();
            else
                $id = $this->GetPrimaryKeyValue($item);
        }

        if (!$id) throw new Exception('Primary key is empty');
        if ($this->IsExisted($id)) throw new Exception("Primary key '$id' has already existed");

        $item[$pk] = $id;

        foreach ($this->fields as $field_name => $field)
        {
            $row[$field_name] = $this->GetFieldValue($item, $field);
        }
        $this->SetRow($id, $row);
        $this->SaveRows();
        return $id;
    }

    protected function GetFieldValue($item, $field)
    {
        $field_name = $field->name;
        if (isset($item[$field_name]))
            $value = $item[$field_name];
        else if ($field->nullable)
            $value = null;
        else if ($field->default)
            $value = $field->default;
        else
            throw new Exception("'$field_name' can not be null");
        return $value;
    }

    protected function GetAutoIncrement()
    {
        $keys = array_keys($this->rows);
        return array_pop($keys) + 1;
    }

    protected function GetPrimaryKeyValue(&$item)
    {
        $pk = $this->GetPrimaryKey();
        if ($pk)
        {
            $id = &$item[$pk];
            if (!$id)
                $id = $this->GetAutoIncrement();
            return $id;
        }
        else return null;
    }

    public function IsExisted($id)
    {
        $pk = $this->GetPrimaryKey();
        if (!$pk)
            throw new Exception('This model does not have primary key');

        return isset($this->rows[$id]);
    }

    public function Count($condition = null)
    {
        return count($this->Find($condition));
    }
}
?>
